package com.joolun.web.controller.common;

import com.alibaba.fastjson2.JSONObject;
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClient;
import com.aliyun.oss.OSSException;
import com.aliyun.oss.common.utils.BinaryUtil;
import com.aliyun.oss.internal.OSSHeaders;
import com.aliyun.oss.model.*;
import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.auth.sts.AssumeRoleRequest;
import com.aliyuncs.auth.sts.AssumeRoleResponse;
import com.aliyuncs.exceptions.ClientException;
import com.aliyuncs.http.MethodType;
import com.aliyuncs.profile.DefaultProfile;
import com.aliyuncs.profile.IClientProfile;
import com.google.common.collect.Maps;
import com.joolun.common.annotation.Anonymous;
import com.joolun.common.config.oss.CloudStorageConfig;
import com.joolun.common.config.oss.OSSFactory;
import com.joolun.common.core.controller.BaseController;
import com.joolun.common.core.domain.AjaxResult;
import com.joolun.common.utils.StringUtils;
import com.joolun.common.utils.file.FileProperties;
import com.joolun.common.utils.uuid.IdUtils;
import io.swagger.annotations.*;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FilenameUtils;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * 文件上传
 *
 * @author ace
 */
@Slf4j
@Api(tags = "Aliyun文件上传")
@RestController
@RequestMapping("/common/oss")
public class OSSController extends BaseController {
    private final OSSFactory ossFactory;
    private final CloudStorageConfig config;
    private final FileProperties fileProperties;


    public OSSController(final OSSFactory ossFactory, final CloudStorageConfig config, final FileProperties fileProperties) {
        this.ossFactory = ossFactory;
        this.config = config;
        this.fileProperties = fileProperties;
    }

    @ApiOperation(value = "删除文件", httpMethod = "DELETE")
    @DeleteMapping("/remove")
    public AjaxResult<Object> remove(@ApiParam("文件路径") @RequestParam String url) {
        String remove = ossFactory.build().remove(url);
        HashMap<String, String> map = new HashMap<>();
        map.put("message", remove);
        return AjaxResult.success(map);
    }

    @ApiOperation(value = "OSS获取Token", httpMethod = "GET")
    @ApiImplicitParams({@ApiImplicitParam(name = "dir", value = "上传的跟路径", dataType = "String", paramType = "query", allowMultiple = false),})
    @RequestMapping("/token/get")
    public Map<String, Object> getOSSToken(@RequestParam(required = false) String dir) {
//        ParamUtils.verify("上传目录", dir, ParamUtils.STRING_NOT_EMPTY_VERIFY_AND_CONVERT_VALUE);

        String bucket = config.getAliyunBucketName();
        String host = StringUtils.isNotBlank(config.getAliyunDomain()) ? config.getAliyunDomain() : "http://" + bucket + "." + config.getAliyunEndPoint();
//		String host = "http://" + bucket + "." + config.getAliyunEndPoint();
        OSSClient client = new OSSClient(config.getAliyunEndPoint(), config.getAliyunAccessKeyId(), config.getAliyunAccessKeySecret());

        try {
            long expireTime = 30 * 60 * 100;
            long expireEndTime = System.currentTimeMillis() + expireTime * 1000;
            Date expiration = new Date(expireEndTime);
            PolicyConditions policyConds = new PolicyConditions();
            policyConds.addConditionItem(PolicyConditions.COND_CONTENT_LENGTH_RANGE, 0, 1048576000);
            policyConds.addConditionItem(MatchMode.StartWith, PolicyConditions.COND_KEY, dir);

            String postPolicy = client.generatePostPolicy(expiration, policyConds);
            byte[] binaryData = postPolicy.getBytes(StandardCharsets.UTF_8);
            String encodedPolicy = BinaryUtil.toBase64String(binaryData);
            String postSignature = client.calculatePostSignature(postPolicy);

            Map<String, Object> respMap = Maps.newLinkedHashMap();
            respMap.put("accessid", config.getAliyunAccessKeyId());
            respMap.put("policy", encodedPolicy);
            respMap.put("signature", postSignature);
            // respMap.put("expire", formatISO8601Date(expiration));
            respMap.put("dir", dir);
            respMap.put("host", host);
            respMap.put("url", host);
            respMap.put("expire", String.valueOf(expireEndTime));

            JSONObject jasonCallback = new JSONObject();
            jasonCallback.put("callbackUrl", config.getAliyunCallbackUrl());
            jasonCallback.put("callbackBody", "filename=${object}&size=${size}&mimeType=${mimeType}&height=${imageInfo.height}&width=${imageInfo.width}");
            jasonCallback.put("callbackBodyType", "application/x-www-form-urlencoded");
            String base64CallbackBody = BinaryUtil.toBase64String(jasonCallback.toString().getBytes());
            respMap.put("callback", base64CallbackBody);

            log.info("-------oss:{}", JSONObject.toJSONString(respMap));
            return AjaxResult.success(respMap);
        } catch (Exception e) {
            AjaxResult.error(e.getMessage());
        }
        return AjaxResult.success();
    }


    @RequestMapping("/callback")
    @ApiOperation("上传回调")
    @Anonymous
    public ResponseEntity<Object> callback(HttpServletRequest request) throws IOException {
        log.info("-----------进入直传oss回调------:{}",request.getParameter("id"));

        Map<String, String> map = new HashMap<>();
        map.put("String value", "ok");
        map.put("Key", "Status");

        String ossCallbackBody = GetPostBody(request.getInputStream(), Integer.parseInt(request.getHeader("content-length")));
        log.info("-----------直传oss回调:{}", ossCallbackBody);
        return new ResponseEntity<>(map, HttpStatus.OK);
    }

    @GetMapping("/stsToken")
    @Anonymous
    @ApiOperation(value = "获取STS临时访问token", httpMethod = "GET")
    public Map<String, Object> StsTokenServer() {
        String accessKeyID = config.getStsAccessKeyId();
        String accessKeySecret = config.getStsAccessKeySecret();
        String roleArn = config.getStsRoleArn();
        String policy = config.getStsPolicy();

        String fileKey = System.currentTimeMillis() + IdUtils.generateRandomKey(6);

        // STS接入地址，例如sts.cn-hangzhou.aliyuncs.com。
        String endpoint = "sts.cn-hangzhou.aliyuncs.com";
        // 自定义角色会话名称，用来区分不同的令牌，例如可填写为SessionTest。
        String roleSessionName = "RamOssTest";
        // 以下Policy用于限制仅允许使用临时访问凭证向目标存储空间examplebucket下的src目录上传文件。
        // 临时访问凭证最后获得的权限是步骤4设置的角色权限和该Policy设置权限的交集，即仅允许将文件上传至目标存储空间examplebucket下的src目录。
        // 如果policy为空，则用户将获得该角色下所有权限。
        //String policy = "{\"Version\": \"1\", \"Statement\": [\"Action\": [\"oss:PutObject\"],\"Resource\": [\"acs:oss:*:*:duofu/*\" ], \"Effect\": \"Allow\"}]\"}";
        Map<String, Object> respMap = new LinkedHashMap<String, Object>();

        // 设置临时访问凭证的有效时间为3600秒。
        Long durationSeconds = 3600L;
        try {
            // regionId表示RAM的地域ID。以华东1（杭州）地域为例，regionID填写为cn-hangzhou。也可以保留默认值，默认值为空字符串（""）。
            String regionId = "";
            // 添加endpoint。适用于Java SDK 3.12.0及以上版本。
            DefaultProfile.addEndpoint(regionId, "Sts", endpoint);
            // 构造default profile。
            IClientProfile profile = DefaultProfile.getProfile(regionId, accessKeyID, accessKeySecret);
            // 构造client。
            DefaultAcsClient client = new DefaultAcsClient(profile);
            final AssumeRoleRequest request = new AssumeRoleRequest();
            // 适用于Java SDK 3.12.0及以上版本。
            request.setSysMethod(MethodType.POST);
            request.setRoleArn(roleArn);
            request.setRoleSessionName(roleSessionName);
            request.setPolicy(policy);
            request.setDurationSeconds(durationSeconds);
            final AssumeRoleResponse response = client.getAcsResponse(request);


            respMap.put("StatusCode", "200");
            respMap.put("AccessKeyId", response.getCredentials().getAccessKeyId());
            respMap.put("AccessKeySecret", response.getCredentials().getAccessKeySecret());
            respMap.put("SecurityToken", response.getCredentials().getSecurityToken());
            respMap.put("Expiration", response.getCredentials().getExpiration());
            respMap.put("RequestId", response.getRequestId());


            Map<String, String> callback = new HashMap<>();
            callback.put("callbackBodyType", "application/x-www-form-urlencoded");
            callback.put("callbackUrl", "https://xcyk-web.xingchenkejixq.cn/prod-api/common/oss/callback");
            callback.put("callbackBody", "id="+fileKey+"&filename=${object}&type=video&etag=${etag}&size=${size}&mimeType=${mimeType}");
            respMap.put("callback", callback);
            respMap.put("fileKey", fileKey);

        } catch (ClientException e) {
            respMap.put("StatusCode", "-1");
            respMap.put("ErrorMSg", e.getErrMsg());
            System.out.println("RequestId: " + e.getRequestId());
            log.error("STS获取token异常:{},{}", e.getErrCode(), e.getErrMsg());
        }
        log.info("------STS OSS返回:{}", JSONObject.toJSONString(respMap));
        return respMap;
    }


    /**
     * 获取Post消息体
     */
    public String GetPostBody(InputStream is, int contentLen) {
        if (contentLen > 0) {
            int readLen = 0;
            int readLengthThisTime = 0;
            byte[] message = new byte[contentLen];
            try {
                while (readLen != contentLen) {
                    readLengthThisTime = is.read(message, readLen, contentLen - readLen);
                    if (readLengthThisTime == -1) {// Should not happen.
                        break;
                    }
                    readLen += readLengthThisTime;
                }
                return new String(message);
            } catch (IOException ignored) {
            }
        }
        return "";
    }


    @PostMapping("/splitUpload")
    @ApiOperation("文件分片上传")
    @CrossOrigin
    public AjaxResult<Object> fragmentation(HttpServletRequest req) {
        //resp.addHeader("Access-Control-Allow-Origin", "*");
        Map<String, Object> map = new HashMap<>();

        MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) req;

        // 获得文件分片数据
        MultipartFile file = multipartRequest.getFile("data");
        // 分片第几片
        int index = Integer.parseInt(multipartRequest.getParameter("index"));
        // 总片数
        int total = Integer.parseInt(multipartRequest.getParameter("total"));
        log.info("开始分片上传文件，总片数：{},第几片：{}", total, index);
        // 获取文件名
        String fileName = multipartRequest.getParameter("name");
        String name = fileName.substring(0, fileName.lastIndexOf("."));
        // 前端uuid，作为标识
        String uuid = multipartRequest.getParameter("uuid");

        // 上传临时文件，等待合并
        File uploadFile = new File(fileProperties.getPath().getFileUploadTempDir() + uuid, uuid + name + index + ".tem");
        if (!uploadFile.getParentFile().exists()) {
            if (!uploadFile.getParentFile().mkdirs()) {
                map.put("status", 502);
                return AjaxResult.success(map);
            }
        }
        if (index < total) {
            try {
                assert file != null;
                file.transferTo(uploadFile);
                // 上传的文件分片名称
                map.put("status", 201);
                return AjaxResult.success(map);
            } catch (IOException e) {
                logger.error("分片上传失败:{}", e.getMessage());
                map.put("status", 502);
                return AjaxResult.success(map);
            }
        } else {
            try {
                assert file != null;
                file.transferTo(uploadFile);
                // 上传的文件分片名称
                map.put("status", 200);
            } catch (IOException e) {
                logger.error("分片上传失败：{}", e.getMessage());
                map.put("status", 502);
            }
            return AjaxResult.success(map);
        }
    }


//    @ResponseBody
//    @GetMapping("/merge")
//    @ApiOperation("分片合并")
//    public RR merge(String uuid, String newFileName) {
//        log.info("------分片合并:{}", newFileName);
//        Map retMap = new HashMap();
//        try {
//            File dirFile = new File(fileProperties.getPath().getFileUploadTempDir() + uuid);
//            if (!dirFile.exists()) {
//                throw new RuntimeException("分片临时文件不存在！");
//            }
//            //分片上传的文件已经位于同一个文件夹下，方便寻找和遍历（当文件数大于十的时候记得排序用冒泡排序确保顺序是正确的）
//            String[] fileNames = dirFile.list();
//            String name = newFileName.substring(0, newFileName.lastIndexOf("."));
//            Arrays.sort(fileNames, (o1, o2) -> {
//                int i1 = Integer.parseInt(o1.substring(o1.indexOf(name) + name.length()).split("\\.tem")[0]);
//                int i2 = Integer.parseInt(o2.substring(o2.indexOf(name) + name.length()).split("\\.tem")[0]);
//                return i1 - i2;
//            });
//
//            // 创建空的合并文件
//            File targetFile = new File(fileProperties.getPath().getFileUploadDir(), newFileName);
//            if (!targetFile.getParentFile().exists()) {
//                targetFile.getParentFile().mkdirs();
//            }
//            RandomAccessFile writeFile = new RandomAccessFile(targetFile, "rw");
//
//            long position = 0;
//            for (String fileName : fileNames) {
//                System.out.println(fileName);
//                File sourceFile = new File(fileProperties.getPath().getFileUploadTempDir() + uuid, fileName);
//                RandomAccessFile readFile = new RandomAccessFile(sourceFile, "rw");
//                int chunkSize = 1024 * 3;
//                byte[] buf = new byte[chunkSize];
//                writeFile.seek(position);
//                int byteCount;
//                while ((byteCount = readFile.read(buf)) != -1) {
//                    if (byteCount != chunkSize) {
//                        byte[] tempBytes = new byte[byteCount];
//                        System.arraycopy(buf, 0, tempBytes, 0, byteCount);
//                        buf = tempBytes;
//                    }
//                    writeFile.write(buf);
//                    position = position + byteCount;
//                }
//                readFile.close();
//                //删除缓存的临时文件
//                org.apache.commons.io.FileUtils.deleteQuietly(sourceFile);
//            }
//            writeFile.close();
//            retMap.put("code", "200");
//
//            String md5Code = FileUtil.getMd5(targetFile);
//            // 查询文件md5值是否存在
//            CsFilePO csFilePO = csFileService.repository().findCsFilePOByCode(md5Code);
//            if (csFilePO != null) {
//                retMap.put("duration", csFilePO.getDuration());
//                retMap.put("size", csFilePO.getFileSize());
//                retMap.put("height", csFilePO.getHeight());
//                retMap.put("width", csFilePO.getWidth());
//                retMap.put("url", csFilePO.getUrl());
//                return RR.data(retMap);
//            }
//            // 合并后的文件转码
//            log.info("---------开始转码：{}", targetFile.getPath());
//
//
//            // TODO：限制为视频文件
//            Map fileFormatPathMap = new ConverVideoUtils().beginConver(targetFile.getPath());
//            String fileFormatPath = fileFormatPathMap.get("path").toString();
//
//
//            // 获取视频宽高
//            Map videoInfoMap = ConverVideoUtils.getVideoInfo(fileFormatPath);
//            String videoDuration = videoInfoMap.get("videoDuration").toString();
//            log.info("---------转码后的文件：{},时长00:00:00格式：{}", fileFormatPath, videoDuration);
//            //将时长转换为秒数
//            //int videoSecond = DateUtils.parseTimeToSecond(videoDuration);
//            //log.info("将时长转换为秒数:{}",videoSecond);
//
//            // 转码文件上传oss
//            log.info("---------转码文件上传oss开始");
//            MultipartFile multipartFile = ConverVideoUtils.getMultipartFile(fileFormatPath);
//            try {
//                RR data = upload(multipartFile, "video", null);
//
//                log.info("---------转码文件上传oss完成：{}", data.getData());
//                retMap.put("duration", videoDuration);
//                retMap.put("size", fileFormatPathMap.get("fileLength"));
//                retMap.put("height", videoInfoMap.get("videoHigh"));
//                retMap.put("width", videoInfoMap.get("videoWide"));
//                retMap.putAll((Map) data.getData());
//
//                // 保存上传文件的md5值
//                CsFile csFile = new CsFile();
//                csFile.setFileCode(md5Code);
//                csFile.setFileName(targetFile.getPath());
//                csFile.setFileOriginalName(multipartFile.getOriginalFilename());
//                long siee = multipartFile.getSize();
//                String contentType = multipartFile.getContentType();
//                csFile.setHeight(videoInfoMap.get("videoHigh").toString());
//                csFile.setWidth(videoInfoMap.get("videoWide").toString());
//                csFile.setUrl(((Map) data.getData()).get("url").toString());
//                csFile.setFileSize(Integer.parseInt(fileFormatPathMap.get("fileLength").toString()));
//                csFile.setDuration(Integer.parseInt(videoDuration));
//                csFileService.repository().save(csFile);
//            } catch (Exception e) {
//                e.printStackTrace();
//            }
//            // 上传成功后删除源转码文件
//            ConverVideoUtils.deleteFile(fileFormatPath);
//
//
////            new ConverVideoUtils().videoConvert(ffMpegPath, targetFile.getPath(), "D:\\mnt\\test4.mp4");
//
//        } catch (IOException e) {
//            e.printStackTrace();
//            retMap.put("code", "500");
//        }
//        return RR.data(retMap);
//    }


    @PostMapping("/uploadSts")
    @ApiOperation(value = "直传文件", notes = "", httpMethod = "POST")
    public AjaxResult<String> uploadStsOss(@RequestPart("file") MultipartFile file) throws Exception {
        // 调用sts获取临时凭证
        Map<String, Object> getTokenMap = StsTokenServer();

        byte[] targetFile = file.getBytes();
        InputStream in = new ByteArrayInputStream(targetFile);

        String endpoint = "https://oss-cn-hangzhou.aliyuncs.com";
        String backName = "xcyk-file";

        String accessKeyID = getTokenMap.get("AccessKeyId").toString();
        String accessKeySecret = getTokenMap.get("AccessKeySecret").toString();
        String securityToken = getTokenMap.get("SecurityToken").toString();

        String suffix = FilenameUtils.getExtension(file.getOriginalFilename());

        String fileName = System.currentTimeMillis() + "." + suffix;
        // 创建OSSClient实例。
        OSS ossClient = new OSSClient(endpoint, accessKeyID, accessKeySecret, securityToken);
        // 将本地文件exampletest.txt上传至目标存储空间examplebucket下的src目录。
        PutObjectRequest putObjectRequest = new PutObjectRequest(backName, fileName, in);

//         ObjectMetadata metadata = new ObjectMetadata();
//         //上传文件时设置存储类型。
//         metadata.setHeader(OSSHeaders.OSS_STORAGE_CLASS, StorageClass.Standard.toString());
//         //上传文件时设置读写权限ACL。
//         metadata.setObjectAcl(CannedAccessControlList.Private);
//         putObjectRequest.setMetadata(metadata);

        try {
            // 上传文件。
            ossClient.putObject(putObjectRequest);

        } catch (OSSException oe) {
            System.out.println("Caught an OSSException, which means your request made it to OSS, "
                    + "but was rejected with an error response for some reason.");
            System.out.println("Error Message:" + oe.getErrorMessage());
            System.out.println("Error Code:" + oe.getErrorCode());
            System.out.println("Request ID:" + oe.getRequestId());
            System.out.println("Host ID:" + oe.getHostId());
        } finally {
            ossClient.shutdown();
        }
        return AjaxResult.success(config.getAliyunDomain() + "/" + fileName);
    }


}

